%%%-------------------------------------------------------------------
%%% @author liuwentao
%%% @doc
%%%     角色分组管理进程
%%% @end
%%% Created : 08. 7月 2021 16:36
%%%-------------------------------------------------------------------
-module(role_group).
-author("liuwentao").

-export([
    start_link/1
    , members/1
    , join/2
    , leave/2
    , apply/2
    , send/2
    , pack_send/3
    , pack_cast/3
    , srv_pack/2
]
).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-record(state, {}).
-include("common.hrl").
-include("group.hrl").
-include("role.hrl").

%% ----------------------------------------------------
%% 外部接口
%% ----------------------------------------------------

%% 分组表
tab(world) -> role_group;

tab(center_world) -> center_group.

%% 所有分组
tab_all() -> [world].
tab_all(center) -> [center_world].

%% 启动
start_link(Mod) ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [Mod], []).

%% @doc 获取指定分组的组员列表
-spec members(atom() | {atom(), any()}) -> {ok, [#member{}]} | {error, term()}.
members(Name) ->
    case catch ets:tab2list(tab(Name)) of
        L when is_list(L) -> {ok, L};
        {'EXIT', {badarg, _}} -> {error, role_group_not_running};
        Err -> {error, Err}
    end.

%% @doc 加入指定的分组
%% <div>注意:加入分组时不判断是否已经存在，需要调用者保证唯一</div>
-spec join(atom() | {atom(), any()}, #member{}) -> ok | {error, term()}.
join(Name, Info) ->
    ets:insert(tab(Name), Info).

%% @doc 退出分组
-spec leave(atom() | {atom(), any()}, pid()) -> ok | {error, term()}.
leave(Name, RolePid) ->
    ets:delete(tab(Name), RolePid).

%% @doc 对指定分组的所有角色执行apply
-spec apply(atom() | {atom(), any()}, {atom(), atom(), list()} |  {atom(), list()}) -> ok | {error, term()}.
apply(Name, {F, A}) ->
    case members(Name) of
        {error, Reason} -> {error, Reason};
        {ok, L} -> do_apply(L, {F, A})
    end;
apply(Name, {M, F, A}) ->
    case members(Name) of
        {error, Reason} -> {error, Reason};
        {ok, L} -> do_apply(L, {M, F, A})
    end.

%% @doc 对指定分组的所有角色发送socket数据
-spec send(atom() | {atom(), any()}, binary()) -> ok | {error, term()}.
send(Name, Bin) ->
    case members(Name) of
        {error, Reason} -> {error, Reason};
        {ok, L} -> do_send(L, Bin)
    end.

%% @doc 对指定分组的所有角色打包并发送socket数据
-spec pack_send(atom() | {atom(), any()}, pos_integer(), tuple()) -> ok | {error, term()}.
pack_send(Name, Code, Data) ->
    case members(Name) of
        {error, Reason} ->
            {error, Reason};
        {ok, L} ->
            case mapping:module(game_server, Code) of
                {ok, _Auth, _Caller, Parser, _ModName} ->
                    case catch Parser:pack(srv, Code, Data) of
                        {ok, Bin} -> do_send(L, Bin);
                        Err ->
                            ?ERR("打包数据出错[Code:~w][Err:~w]", [Code, Err])
                    end;
                {error, _Code} ->
                    ?ERR("模块影射失败[~w]:~w", [Code, Data])
            end
    end.

%% @doc 打包并发送消息到角色进程
-spec pack_cast(Pids, Code, Data) -> ok when
    Pids :: [pid()],
    Code :: pos_integer(),
    Data :: tuple().
pack_cast(Pids, Code, Data) ->
    case mapping:module(game_server, Code) of
        {ok, _Auth, _Caller, Proto, _ModName} ->
            case Proto:pack(srv, Code, Data) of
                {ok, Bin} ->
                    lists:foreach(fun(Pid) ->
                        case is_pid(Pid) of
                            true -> Pid ! {socket_proxy, Bin};
                            false -> ignore
                        end
                                  end, Pids);
                {error, Reason} ->
                    ?ERR("打包数据出错[Code:~w][Data:~w][Reason:~w]", [Code, Data, Reason])
            end;
        {error, _Code} ->
            ?ERR("模块影射失败[~w]:~w", [Code, Data])
    end.

%%打包协议数据
srv_pack(Code, Data) ->
    case mapping:module(game_server, Code) of
        {ok, _Auth, _Caller, Proto, _ModName} ->
            case Proto:pack(srv, Code, Data) of
                {ok, Bin} -> Bin;
                _ -> <<>>
            end;
        _ ->
            <<>>
    end.


%% ----------------------------------------------------
%% 内部处理
%% ----------------------------------------------------

init([Mod]) ->
    ?INFO("[~w] 正在启动...", [?MODULE]),
    process_flag(trap_exit, true),
    case Mod of
        center ->
            [ets:new(tab(Name), [set, named_table, public, {keypos, #member.pid}]) || Name <- tab_all(center)];
        _ ->
            [ets:new(tab(Name), [set, named_table, public, {keypos, #member.pid}]) || Name <- tab_all()]
    end,
    State = #state{},
    ?INFO("[~w] 启动完成", [?MODULE]),
    {ok, State}.

handle_call(_Request, _From, State) ->
    {noreply, State}.

handle_cast(_Msg, State) ->
    {noreply, State}.

handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    ?INFO("[~w] 正在关闭...", [?MODULE]),
    ?INFO("[~w] 关闭完成", [?MODULE]),
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%% ----------------------------------------------------
%% 私有函数
%% ----------------------------------------------------

%% 执行apply
do_apply([], _) -> ok;
do_apply([#member{pid = Pid} | T], {M, F, A}) ->
    role:apply(async, Pid, {M, F, A}),
    do_apply(T, {M, F, A});
do_apply([#member{pid = Pid} | T], {F, A}) ->
    role:apply(async, Pid, {F, A}),
    do_apply(T, {F, A}).

%% 执行socket数据发送
do_send([], _) -> ok;
do_send([#member{conn_pid = Pid} | T], Bin) ->
    Pid ! {send_data, Bin},
    do_send(T, Bin).


